//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/os/capplication.h>
#include <buola/os/cappaction.h>

#include <buola/utility/cdesktopfile.h>
#include <buola/os/cprocess.h>
#include <buola/algorithm/comparison.h>
#include <buola/io/cfolder.h>
#include <buola/io/cresourcefinder.h>
#include <buola/algorithm/case.h>

#include <unordered_map>

namespace buola { namespace os {

struct CApplicationPath
{
    io::CURI mPath;
    std::map<std::string,io::CURI> mEntries; //as relative path to mPath
};
    
static std::unordered_map<std::string,CApplication*> sAllApps;          //by ID name
static std::unordered_map<std::string,CApplication*> sAllAppsByClass;   //by WM class
static bool sDefaultPathsLoaded=false;

static std::map<std::string,CApplicationPath*> sAppPaths;
static bool sDefaultPathsScanned=false;

CApplication::CApplication(const io::CURI &pURI,const std::string &pIDName)
    :   mURI(pURI)
    ,   mIDName(pIDName)
{
    CDesktopFile lDesktopFile(pURI);

    std::string lTemp;
    lDesktopFile.Get("Desktop Entry","Type",lTemp);

    if(lTemp!="Application") throw XData("malformed application desktop file");
    if(!lDesktopFile.Get("Desktop Entry","Name",mName)) throw XData("malformed application desktop file");
    if(!lDesktopFile.Get("Desktop Entry","Exec",mExec)) throw XData("malformed application desktop file");
    
    lDesktopFile.Get("Desktop Entry","GenericName",mGenericName);
    mNoDisplay=lDesktopFile.GetValue<bool>("Desktop Entry","NoDisplay",false);
    lDesktopFile.Get("Desktop Entry","Comment",mComment);
    lDesktopFile.Get("Desktop Entry","Icon",mIcon);
    mDeleted=lDesktopFile.GetValue<bool>("Desktop Entry","Hidden",false);
    lDesktopFile.Get("Desktop Entry","Path",mPath);
    mTerminal=lDesktopFile.GetValue<bool>("Desktop Entry","Terminal",false);
    
    std::vector<std::string> lMimeTypes;
    lDesktopFile.Get("Desktop Entry","Categories",lMimeTypes);
    for(int i=0;i<lMimeTypes.size();i++)
    {
        if(!lMimeTypes[i].empty())
            mMimeTypes.insert(lMimeTypes[i]);
    }

    std::vector<std::string> lCategories;
    lDesktopFile.Get("Desktop Entry","Categories",lCategories);
    for(int i=0;i<lCategories.size();i++)
    {
        if(!lCategories[i].empty())
            mCategories.insert(lCategories[i]);
    }

    mStartupNotify=lDesktopFile.GetValue<bool>("Desktop Entry","StartupNotify",false);
    lDesktopFile.Get("Desktop Entry","StartupWMClass",mWMClass);
    if(mWMClass.empty())
        mWMClass=pURI.NameWithoutExtension();

    if(!mDeleted) //also delete if we can't execute it
    {
        std::string lTryExec;
        if(lDesktopFile.Get("Desktop Entry","TryExec",lTryExec))
        {
            //TODO:check if it can be executed
        }
    }
    
    if(!mNoDisplay)
    {
        if(mDeleted)
            mNoDisplay=true;
    
        //set no display if it shouldn't be displayed in our desktop
        std::vector<std::string> lList;
        if(lDesktopFile.Get("Desktop Entry","OnlyShowIn",lList))
        {
            mNoDisplay=true;
            for(int i=0;i<lList.size();i++)
            {
                if(lList[i]=="buola")
                {
                    mNoDisplay=false;
                    break;
                }
            }
        }
        else if(lDesktopFile.Get("Desktop Entry","NotShowIn",lList))
        {
            for(int i=0;i<lList.size();i++)
            {
                if(lList[i]=="buola")
                {
                    mNoDisplay=true;
                    break;
                }
            }
        }
    }
    
    mActions.push_back(new CAppAction(this));
}

void CApplication::Run()
{
    std::string lExec;

    for(int i=0;i<mExec.length();i++)
    {
        if(mExec[i]=='%')
        {
            i++;
        }
        else if(mExec[i]==' ')
        {
            break;
        }
        else
        {
            lExec+=mExec[i];
        }
    }

    CProcess lProcess(lExec,CProcess::DETACH|CProcess::SEARCH_PATH);
    lProcess.Launch();
}

static void scan_path_helper(CApplicationPath *pPath,const io::CURI &pURI,const std::string &pPrefix)
{
    io::CFolder lFolder(pURI);
    std::vector<io::CURI> lURIs=lFolder.GetChildrenURIs();

    for(int i=0;i<lURIs.size();i++)
    {
        if(lURIs[i].Extension()=="desktop")
        {
            pPath->mEntries[pPrefix+lURIs[i].NameWithoutExtension()]=lURIs[i];
        }
        else
        {
            io::CFile lFile(lURIs[i]);
            if(lFile.IsFolder())
            {
                scan_path_helper(pPath,lURIs[i],pPrefix+lURIs[i].Name()+'-');
            }
        }
    }
}

static CApplicationPath *scan_path(const io::CURI &pURI)
{
    std::string lURI=pURI.ToString();
    auto i=sAppPaths.find(lURI);
    if(i!=sAppPaths.end()) return i->second;

    CApplicationPath *lPath=new CApplicationPath;
    scan_path_helper(lPath,pURI,"");
    sAppPaths[lURI]=lPath;
    return lPath;
}

static void scan_default_paths()
{
    if(sDefaultPathsScanned)
        return;
    sDefaultPathsScanned=true;
    
    std::vector<io::CURI> lList=io::data_finder().FindFolders("applications");

    for(int i=0;i<lList.size();i++)
        scan_path(lList[i]);
}

static void load_all_in_path(CApplicationPath *pPath)
{
    for(auto i=pPath->mEntries.begin();i!=pPath->mEntries.end();++i)
    {
        if(sAllApps.count(i->first)) continue;
        CApplication::LoadApp(i->second,i->first);
    }
}

CApplication *CApplication::LoadApp(const io::CURI &pURI,const std::string &pIDName)
{
    try
    {
        CApplication *lApp=new CApplication(pURI,pIDName);
        sAllApps[pIDName]=lApp;
        sAllAppsByClass[lApp->GetWMClass()]=lApp;
        return lApp;
    }
    catch(...)
    {
        return nullptr;
    }
}

void CApplication::AddAllInFolder(const io::CURI &pPath,std::set<CApplication*> &pDst)
{
    CApplicationPath *lPath=scan_path(pPath);
    load_all_in_path(lPath);
    
    for(auto i=lPath->mEntries.begin();i!=lPath->mEntries.end();++i)
    {
        auto j=sAllApps.find(i->first);
        if(j!=sAllApps.end())
            pDst.insert(j->second);
    }
}

CApplication *CApplication::Get(const std::string &pIDName)
{
    auto i=sAllApps.find(pIDName);
    if(i!=sAllApps.end())
        return i->second;
    
    if(!sDefaultPathsScanned)
        scan_default_paths();

    for(auto p=sAppPaths.begin();p!=sAppPaths.end();++p)
    {
        auto e=p->second->mEntries.find(pIDName);
        if(e!=p->second->mEntries.end())
        {
            CApplication *lApp=CApplication::LoadApp(e->second,pIDName);
            if(lApp) return lApp;
        }
    }
    
    sAllApps[pIDName]=nullptr;
    return nullptr;
}

CApplication *CApplication::Get(const std::string &pIDName,const io::CURI &pBasePath)
{
    auto i=sAllApps.find(pIDName);
    if(i!=sAllApps.end())
        return i->second;
    
    CApplicationPath *lPath=scan_path(pBasePath);

    auto e=lPath->mEntries.find(pIDName);
    if(e!=lPath->mEntries.end())
    {
        CApplication *lApp=LoadApp(e->second,pIDName);
        if(lApp) return lApp;
    }
    
    return nullptr;
}

void CApplication::LoadDefaultPaths()
{
    if(sDefaultPathsLoaded)
        return;
    sDefaultPathsLoaded=true;
    
    if(!sDefaultPathsScanned)
        scan_default_paths();
    
    for(auto i=sAppPaths.begin();i!=sAppPaths.end();++i)
        load_all_in_path(i->second);
}

CApplication *CApplication::GetByClass(const std::string &pClass)
{
    auto i=sAllAppsByClass.find(tolower_clone(pClass));
    if(i!=sAllApps.end())
        return i->second;
    
    return nullptr;
}

/*namespace os*/ } /*namespace buola*/ }
